The MSMQ binding is designed to be employed in the
intranet. It cannot go through firewalls by default, and more
importantly, it uses a Microsoft-specific encoding and message format.
Even if you could tunnel through the firewall, you would need the other
party to use WCF as well. While requiring WCF at both ends is a
reasonable assumption in the intranet, it is unrealistic to demand that
from Internet-facing clients and services, and it violates the core
service-oriented principles that service boundaries should be explicit
and that the implementation technology used by a service should be
immaterial to its clients. That said, Internet services may benefit from
queued calls just like intranet clients and services, and yet the lack
of an industry standard for such queued interoperability (and the lack
of support in WCF) prevents such interaction. The solution to that
problem is a technique I call the HTTP bridge. The HTTP bridge
is a configuration pattern rather than a set of helper classes. The HTTP
bridge, as its name implies, is designed to provide queued calls support
for clients and services connected over the Internet. The bridge
requires the use of the WSHttpBinding
(rather than the basic binding) because it is a transactional binding.
There are two parts to the HTTP bridge. The bridge enables WCF clients
to queue up calls to an Internet service that uses the WS binding, and
it enables a WCF service that exposes an HTTP endpoint over the WS
binding to queue up calls from its Internet clients. You can use each
part of the bridge separately, or you can use them in conjunction. The
bridge can only be used if the remote service contract can be queued
(that is, if the contract has only one-way operations), but that is
usually the case; otherwise, the client would not have been interested
in the bridge in the first place.
1. Designing the Bridge
Since you cannot really queue up calls with the WS binding, you
can facilitate that instead using an intermediary bridging client and
service. When the client wishes to queue up a call against an
Internet-based service, the client will in fact queue up the call
against a local (that is, intranet-based) queued service called
MyClientHttpBridge. In its
processing of the queued call, the client-side queued bridge service
will use the WS binding to call the remote Internet-based service.
When an Internet-based service wishes to receive queued calls, it will
use a queue. But because non-WCF clients cannot access that queue over
the Internet, the service will use a façade: a dedicated connected
service called MyServiceHttpBridge
that exposes a WS-binding endpoint. In its processing of the Internet
call, MyServiceHttpBridge simply
makes a queued call against the local service. Figure 1 shows the HTTP bridge
architecture.
2. Transaction Configuration
It is important to use transactions between MyClientHttpBridge, the client side of the
bridge, and the remote service, and it is important to configure the
service-side bridge (MyServiceHttpBridge) to use the Client
transaction . The
rationale is that by using a single transaction from the playback of
the client call to the MyClientHttpBridge to the MyServiceHttpBridge (if present) you will
approximate the transactional delivery semantic of a normal queued
call, as shown in Figure 2.
3. Service-Side Configuration
MyServiceHttpBridge converts
a regular connected call over the WS binding into a queued call and
posts it to the service queue. MyServiceHttpBridge implements a contract
that is similar, but not identical, to that of the queued service. The
reason is that the service-side bridge should be able to participate
in the incoming transaction, but transactions cannot flow over one-way
operations. The solution is to modify the contract to support (indeed,
mandate) transactions. For example, if this is the original service
contract:
[ServiceContract]
public interface IMyContract
{
[OperationContract(IsOneWay = true)]
void MyMethod();
}
then MyServiceHttpBridge
should expose this contract instead:
[ServiceContract]
public interface IMyContractHttpBridge
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
void MyMethod();
}
In essence, you need to set
IsOneWay to false and use TransactionFlowOption.Mandatory. For readability’s sake, I
recommend that you also rename the interface by suffixing it with
HttpBridge. MyServiceHttpBridge can be hosted anywhere in the service’s intranet,
including in the service’s own process. Example 1 shows the
required configuration of the service and its HTTP bridge.
Example 1. Service-side configuration of the HTTP bridge
<!-- MyService Config File -->
<services>
<service name = "MyService">
<endpoint
address = "net.msmq://localhost/private/MyServiceQueue"
binding = "netMsmqBinding"
contract = "IMyContract"
/>
</service>
</services>
<!-- MyServiceHttpBridge Config File -->
<services>
<service name = "MyServiceHttpBridge">
<endpoint
address = "http://localhost:8001/MyServiceHttpBridge"
binding = "wsHttpBinding"
bindingConfiguration = "ReliableTransactedHTTP"
contract = "IMyContractHttpBridge"
/>
</service>
</services>
<client>
<endpoint
address = "net.msmq://localhost/private/MyServiceQueue"
binding = "netMsmqBinding"
contract = "IMyContract"
/>
</client>
<bindings>
<wsHttpBinding>
<binding name = "ReliableTransactedHTTP" transactionFlow = "true">
<reliableSession enabled = "true"/>
</binding>
</wsHttpBinding>
</bindings>
|
The service MyService exposes
a simple queued endpoint with IMyContract. The service MyServiceHttpBridge exposes an endpoint with
WSHttpBinding and the IMyContractHttpBridge contract. MyServiceHttpBridge is also a client of the
queued endpoint defined by the service. Example 2 shows the
corresponding implementation. Note that MyServiceHttpBridge is configured for the
Client transaction mode.
Example 2. Service-side implementation of the HTTP bridge
class MyService : IMyContract
{
//This call comes in over MSMQ
[OperationBehavior(TransactionScopeRequired = true)]
public void MyMethod()
{...}
}
class MyServiceHttpBridge : IMyContractHttpBridge
{
//This call comes in over HTTP
[OperationBehavior(TransactionScopeRequired = true)]
public void MyMethod()
{
MyContractClient proxy = new MyContractClient();
//This call goes out over MSMQ
proxy.MyMethod();
proxy.Close();
}
}
|
4. Client-Side Configuration
The client uses queued calls against the local MyClientHttpBridge service. MyClientHttpBridge can be hosted in the same
process as the client, in a different process, or even on a separate
machine on the client’s intranet. The local MyClientHttpBridge service uses the WSHttpBinding to call the remote service.
The client needs to retrieve the metadata of the remote Internet service
(such as the definition of IMyContractHttpBridge) and convert it to a
queued contract (such as IMyContract). Example 3 shows the
required configuration of the client and its HTTP bridge.
Example 3. Client-side configuration of the HTTP bridge
<!-- Client Config File -->
<client>
<endpoint
address = "net.msmq://localhost/private/MyClientHttpBridgeQueue"
binding = "netMsmqBinding"
contract = "IMyContract"
/>
</client>
<!-- MyClientHttpBridge Config File -->
<services>
<service name = "MyClientHttpBridge">
<endpoint
address = "net.msmq://localhost/private/MyClientHttpBridgeQueue"
binding = "netMsmqBinding"
contract = "IMyContract"
/>
</service>
</services>
<client>
<endpoint
address = "http://localhost:8001/MyServiceHttpBridge"
binding = "wsHttpBinding"
bindingConfiguration = "ReliableTransactedHTTP"
contract = "IMyContractHttpBridge"
/>
</client>
<bindings>
<wsHttpBinding>
<binding name = "ReliableTransactedHTTP" transactionFlow = "true">
<reliableSession enabled = "true"/>
</binding>
</wsHttpBinding>
</bindings>
|
MyClientHttpBridge exposes a
simple queued endpoint with IMyContract. MyClientHttpBridge is also a client of the
connected WS-binding endpoint defined by the service. Example 4 shows the
corresponding implementation.
Example 4. Client-side implementation of the HTTP bridge
MyContractClient proxy = new MyContractClient();
//This call goes out over MSMQ
proxy.MyMethod();
proxy.Close();
//////////////// Client-Side Bridge Implementation ////////////
class MyClientHttpBridge : IMyContract
{
//This call comes in over MSMQ
[OperationBehavior(TransactionScopeRequired = true)]
public void MyMethod()
{
MyContractHttpBridgeClient proxy = new MyContractHttpBridgeClient();
//This call goes out over HTTP
proxy.MyMethod();
proxy.Close();
}
}